package repl

import (
	"bufio"
	"fmt"
	"fuck/ast"
	"fuck/compiler"
	"fuck/evaluator"
	"fuck/lexer"
	"fuck/logger"
	"fuck/object"
	"fuck/parser"
	"fuck/vm"
	"io"
)

const PROMPT = logger.Green + ">> " + logger.Default

type VMRunEnvironment struct {
	Constants   []object.Object
	Globals     []object.Object
	SymbolTable *compiler.SymbolTable
}

func Start(in io.Reader, out io.Writer) {
	scanner := bufio.NewScanner(in)
	//env := object.NewEnvironment() //必须调用这个来生成Env，因为map的内存需要手动开辟
	vmEnv := VMRunEnvironment{
		Constants:   []object.Object{},
		Globals:     make([]object.Object, vm.GlobalsSize),
		SymbolTable: compiler.NewSymbolTable(),
	}
	for {
		fmt.Fprintf(out, PROMPT)
		scanned := scanner.Scan()
		if !scanned {
			return
		}
		line := scanner.Text()
		lex := lexer.CreateLexer(line)
		par := parser.CreateParser(lex)
		program := par.ParseProgram()
		if len(par.Errors()) != 0 {
			printParserErrors(out, par.Errors())
			continue
		}
		//RunProgramByInterpreter(program, env,in, out)
		RunProgramByVM(program, &vmEnv, in, out)
		//io.WriteString(out, program.String())
		//io.WriteString(out, "\n")
		//for tok := lex.NextToken(); tok.Type != token.EOF; tok = lex.NextToken() {
		//	fmt.Fprintf(out, "%+v length %d\n", tok, len(tok.Literal))
		//}
	}
}
func RunProgramByVM(program *ast.Program, vmEnv *VMRunEnvironment, in io.Reader, out io.Writer) {
	comp := compiler.NewWithState(vmEnv.SymbolTable, vmEnv.Constants)
	err := comp.Compile(program)
	if err != nil {
		fmt.Fprintf(out, "Compilation failed:\n %s\n", err)
		return
	}
	machine := vm.NewWithGlobalsStore(comp.Bytecode(), vmEnv.Globals)
	err = machine.Run()
	if err != nil {
		fmt.Fprintf(out, "Executing bytecode failed:\n %s\n", err)
	}
	stackTop := machine.LastPopedStackElem()
	io.WriteString(out, stackTop.Inspect())
	io.WriteString(out, "\n")
}

func RunProgramByInterpreter(program *ast.Program, env *object.Environment, in io.Reader, out io.Writer) {

	evaluated := evaluator.Eval(program, env)
	if evaluated != nil {
		if evaluated.Type() == object.ERROR_OBJ {
			logger.Ferrorf(out, evaluated.Inspect()+"\n")
		} else {
			io.WriteString(out, logger.Light+evaluated.Inspect()+"\n"+logger.Default)
		}
	}
}

func printParserErrors(out io.Writer, errors []string) {
	for _, msg := range errors {
		logger.Ferrorf(out, "(Parser) "+msg+"\n")
	}
}
